Visibility
Visibility controls who is allowed to access what.
Rust’s philosophy:
Everything is private by default.
You must explicitly allow access using pub.
Visibility applies to:
- modules
- functions
- structs
- enums
- fields
- methods
- constants
- traits
The pub Keyword – What It Means
pub means:
“This item can be accessed from outside the current module.”
But pub does NOT mean globally accessible.
Visibility is still constrained by:
- module hierarchy
- crate boundaries
The Golden Rule of Visibility
You can only access an item if every module in the path is visible.
This is the most important rule to remember.
Private by Default (No pub)
mod secrets {
fn hidden() {
println!("Top secret");
}
}
fn main() {
// secrets::hidden(); ❌ error
}
Why?
hidden()is private- Only accessible inside
secrets
Making Functions Public
mod secrets {
pub fn reveal() {
println!("Not so secret");
}
}
fn main() {
secrets::reveal(); // ✅
}
Module Visibility (pub mod)
Modules themselves must also be public.
Common mistake
mod api {
pub fn call() {}
}
fn main() {
// api::call(); ❌ api is private
}
Correct version
pub mod api {
pub fn call() {}
}
fn main() {
api::call(); // ✅
}
Visibility Chains (Path Rule)
Broken visibility chain
mod outer {
mod inner {
pub fn run() {}
}
}
fn main() {
// outer::inner::run(); ❌
}
Fixed
pub mod outer {
pub mod inner {
pub fn run() {}
}
}
fn main() {
outer::inner::run(); // ✅
}
Every level in the path must be pub
Struct Visibility
Struct itself vs fields
mod model {
pub struct User {
pub name: String,
age: u8,
}
}
What’s accessible?
use model::User;
fn main() {
let u = User {
name: "Alice".into(),
// age: 30 ❌ private field
};
println!("{}", u.name); // ✅
}
Key rule
pub struct→ type is public- fields are private unless marked
pub
Enum Visibility
Enums are simpler.
pub enum Status {
Active,
Inactive,
}
- All variants are automatically public
- No need to mark each variant
pub
Method Visibility (impl blocks)
Methods follow the same rule.
pub struct Account {
balance: i32,
}
impl Account {
pub fn new() -> Self {
Self { balance: 0 }
}
fn secret_reset(&mut self) {
self.balance = 0;
}
}
new()→ public APIsecret_reset()→ internal helper
Visibility with use
use does not change visibility.
Wrong assumption
mod hidden {
fn secret() {}
}
use hidden::secret; // ❌ still private
Must be public first
mod hidden {
pub fn secret() {}
}
use hidden::secret; // ✅
pub(crate) – Crate-Level Visibility
Rust offers fine-grained visibility.
pub(crate) fn internal_only() {}
Accessible:
- anywhere inside the same crate
- NOT from other crates
Other Visibility Modifiers
| Modifier | Accessible From |
|---|---|
pub | Everywhere |
pub(crate) | Same crate |
pub(super) | Parent module |
pub(self) | Same module only |
mod parent {
pub(super) fn call_parent() {}
}
File-Based Example (Real Structure)
src/
├── lib.rs
└── auth/
├── mod.rs
└── login.rs
lib.rs
pub mod auth;
auth/mod.rs
pub mod login;
auth/login.rs
pub fn login_user() {
println!("Logged in");
}
fn helper() {} // private
External usage
use my_lib::auth::login::login_user;
Re-exporting with pub use
This lets you control your public API.
mod internal {
pub struct User;
}
pub use internal::User;
Result
use my_lib::User;
Users don’t need to know about internal
Binary + Library Visibility Pattern
Recommended design
- Library crate
- exposes pub APIs
- hides internals
- Binary crate
- uses the public API only
// lib.rs
mod engine;
pub use engine::run;
Why Rust Is So Strict About Visibility
Rust forces you to:
- design clean APIs
- avoid accidental coupling
- make internal changes safely
- think in terms of boundaries
This is a feature, not friction.